package com.ybear.ybutils.utils.time;

import static com.ybear.ybutils.utils.time.DateTimeType.A;
import static com.ybear.ybutils.utils.time.DateTimeType.DAY;
import static com.ybear.ybutils.utils.time.DateTimeType.HOUR;
import static com.ybear.ybutils.utils.time.DateTimeType.MILLIS;
import static com.ybear.ybutils.utils.time.DateTimeType.MINUTE;
import static com.ybear.ybutils.utils.time.DateTimeType.MONTH;
import static com.ybear.ybutils.utils.time.DateTimeType.SECOND;
import static com.ybear.ybutils.utils.time.DateTimeType.WEEK_LONG;
import static com.ybear.ybutils.utils.time.DateTimeType.WEEK_OF_YEAR;
import static com.ybear.ybutils.utils.time.DateTimeType.YEAR;
import static com.ybear.ybutils.utils.time.TimeStateType.TYPE_LAST_MONTH;
import static com.ybear.ybutils.utils.time.TimeStateType.TYPE_NEXT_MONTH;
import static com.ybear.ybutils.utils.time.TimeStateType.TYPE_NEXT_YEAR;
import static com.ybear.ybutils.utils.time.TimeStateType.TYPE_NONE;
import static com.ybear.ybutils.utils.time.TimeStateType.TYPE_RECENTLY;
import static com.ybear.ybutils.utils.time.TimeStateType.TYPE_TOMORROW;
import static com.ybear.ybutils.utils.time.TimeStateType.TYPE_YESTERDAY;
import static com.ybear.ybutils.utils.time.TimeStateType.TYPE_YESTERYEAR;
import static com.ybear.ybutils.utils.time.TimeType.TYPE_DAY;
import static com.ybear.ybutils.utils.time.TimeType.TYPE_HOUR;
import static com.ybear.ybutils.utils.time.TimeType.TYPE_MINUTE;
import static com.ybear.ybutils.utils.time.TimeType.TYPE_MONTH;
import static com.ybear.ybutils.utils.time.TimeType.TYPE_SECOND;
import static com.ybear.ybutils.utils.time.TimeType.TYPE_YEAR;

import android.content.res.Resources;
import android.os.Build;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ybear.ybutils.utils.LocaleUtil;
import com.ybear.ybutils.utils.ObjUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

/**
 * 时间日期工具类
 */
public class DateTime {

    /**
     计算年龄
     @param ms    出生时间戳。eg：946656000000（2000-01-01 0:00:00）
     @return      年龄。eg：23（ (20230101-20000101)/10000=23 ）
     */
    public static double calcArgsOfDouble(long ms) {
        double base = 10000D;
        int s = ObjUtils.parseInt( DateTime.parse( ms, YEAR, MONTH, DAY ) );
        int ct = ObjUtils.parseInt( DateTime.nows( YEAR, MONTH, DAY ) );
        return s == 0 || ct == 0 || ct - s < base ?
                0 :
                ObjUtils.parseInt( Math.floor( ( ct - s ) / base ) );
    }

    /**
     计算年龄
     @param ms    出生时间戳。eg：946656000000（2000-01-01 0:00:00）
     @return      年龄。eg：23（ (20230101-20000101)/10000=23 ）
     */
    public static int calcArgs(long ms) {
        return ObjUtils.parseInt( calcArgsOfDouble( ms ) );
    }

    /**
     转换为消息时间
     @param timestamp       时间戳
     @param style           消息样式（本地化）
     @return                刚刚、N分钟前、N小时前、昨天、N天前、MM/dd、yyyy/MM/dd
     */
    public static String toMessageTime(long timestamp, @NonNull MessageTimeStyle style) {
        LeadTime leadTime = toLeadTime( timestamp );
        long diff = leadTime.getDiffTime();
        int state = leadTime.getTimeState();
        switch( leadTime.getTimeType() ) {
            case TYPE_SECOND:       //一分钟之内
                if( style.getRecently() == null ) break;
                return style.getRecently();
            case TYPE_MINUTE:       //N分钟前
                if( style.getMinutesAgo() == null ) break;
                return diff + style.getMinutesAgo();
            case TYPE_HOUR:         //昨天 和 N小时前
                if( style.getHoursAgo() == null ) break;
                //昨天
                if( state == TYPE_YESTERDAY ) return style.getYesterday();
                //N小时前
                return diff + style.getHoursAgo();
            case TYPE_DAY:          //N天前
                if( style.getDayAgo() == null ) break;
                return diff + style.getDayAgo();
            case TYPE_MONTH:
                break;
            case TYPE_YEAR:         //MM/dd
                return DateTime.parse( timestamp, DateTimeType.MONTH, "/", DateTimeType.DAY );
        }
        //yyyy/MM/dd
        return DateTime.parse(
                timestamp, DateTimeType.YEAR, "/" + DateTimeType.MONTH, "/", DateTimeType.DAY
        );
//        long curTime = System.currentTimeMillis();
//        long diff = curTime - timestamp;
//         //一分钟之内
//        if( style.getRecently() != null && diff < 6_0000L ) return style.getRecently();
//        //N分钟前
//        if( style.getMinutesAgo() != null && diff < 360_0000L ) {
//            return ( diff / 60 / 1000 ) + style.getMinutesAgo();
//        }
//        //昨天 和 N小时前
//        if( style.getHoursAgo() != null && diff < 8640_0000L ) {
//            String curDay = DateTime.parse( curTime, DateTimeType.DAY );
//            String ttDay = DateTime.parse( timestamp, DateTimeType.DAY );
//            //昨天
//            if( curDay != null && !curDay.equals( ttDay ) ) return style.getYesterday();
//            //N小时前
//            return ( diff / 60 / 60 / 1000 ) + style.getHoursAgo();
//        }
//        //N天前
//        if( style.getDayAgo() != null && diff <= 25_9200_0000L ) {
//            return ( diff / 24 / 60 / 60 / 1000 ) + style.getDayAgo();
//        }
//        //MM/dd
//        if( diff <= 315_3600_0000L ) {
//            return DateTime.parse( timestamp, DateTimeType.MONTH, "/", DateTimeType.DAY );
//        }
//        //yyyy/MM/dd
//        return DateTime.parse(
//                timestamp, DateTimeType.YEAR, "/" + DateTimeType.MONTH, "/", DateTimeType.DAY
//        );
    }
    public static String toMessageTime(Resources res, long timestamp) {
        return toMessageTime( timestamp, MessageTimeStyle.defaultStyle( res ) );
    }
    public static String toMessageTime(String s, @NonNull MessageTimeStyle style) {
        return toMessageTime( parse( s ), style );
    }
    public static String toMessageTime(Resources res, String s) {
        return toMessageTime( res, parse( s ) );
    }

    /**
     * 时间差。当前时间与传入时间的差值
     * @param fromTimeMillis    开始时间（当前时间） {@link System#currentTimeMillis()}
     * @param toTimeMillis      求差时间（目标时间）
     * @param leadTime          时间差值实体类
     */
    public static void toLeadTime(long fromTimeMillis, long toTimeMillis, @Nullable LeadTime leadTime) {
        if( leadTime == null ) leadTime = new LeadTime();
        String patterns = String.format(
                "%s%s%s", DateTimeType.YEAR, DateTimeType.MONTH, DateTimeType.DAY
        );
        //eg: 20230101
        long fromFormat = ObjUtils.parseLong( DateTime.parse( fromTimeMillis, patterns ));
        //eg: 20221231
        long toFormat = ObjUtils.parseLong( DateTime.parse( toTimeMillis, patterns ));
        long diff = fromTimeMillis - toTimeMillis;
        //一分钟之内 6_0000/1000=60
        if( diff < 6_0000L ) {
            leadTime.setTimeState( TYPE_RECENTLY ).setLeadTime( diff, TYPE_SECOND );
            return;
        }
        //N分钟前 360_0000/60/1000=60
        if( diff < 360_0000L ) {
            leadTime.setLeadTime( diff / 60, TYPE_MINUTE ).setTimeState( TYPE_NONE );
            return;
        }
        /* 昨天 和 N小时前 */
        if( diff < 8640_0000L || fromFormat > toFormat ) {
            //N小时前 8640_0000/60/60/1000=24
            leadTime.setLeadTime( diff / 60 / 60, TYPE_HOUR )
                    //昨天 或 明天
                    .setTimeState(
                            toTimeState( fromTimeMillis, toTimeMillis, DAY, TYPE_YESTERDAY, TYPE_TOMORROW )
                    );
            return;
        }
        //N天前 25_9200_0000/24/60/60/1000=30
        if( diff <= 25_9200_0000L ) {
            leadTime.setLeadTime( diff / 24 / 60 / 60, TYPE_DAY )
                    .setTimeState(
                    //上个月 或 下个月
                    toTimeState( fromTimeMillis, toTimeMillis, MONTH, TYPE_LAST_MONTH, TYPE_NEXT_MONTH )
            );
            return;
        }
        //N月前 和 N年前
        if( diff <= 315_3600_0000L ) {
            //N月前 315_3600_0000/30/24/60/60/1000=12
            leadTime.setLeadTime( diff / 30 / 24 / 60 / 60, TYPE_MONTH )
                    .setTimeState( TYPE_NONE );
        }else {
            //N年前 315_3600_0000/365/24/60/60/1000=1
            leadTime.setLeadTime( diff / 365 / 24 / 60 / 60, TYPE_YEAR )
                    .setTimeState(
                            //去年 或 明年
                            toTimeState( fromTimeMillis, toTimeMillis, YEAR, TYPE_YESTERYEAR, TYPE_NEXT_YEAR )
                    );
        }
    }
    public static LeadTime toLeadTime(long fromTimeMillis, long toTimeMillis) {
        LeadTime leadTime = new LeadTime();
        toLeadTime( fromTimeMillis, toTimeMillis, leadTime );
        return leadTime;
    }
    public static void toLeadTime(long toTimeMillis, @Nullable LeadTime leadTime) {
        toLeadTime( System.currentTimeMillis(), toTimeMillis, leadTime );
    }
    public static LeadTime toLeadTime(long toTimeMillis) {
        return toLeadTime( System.currentTimeMillis(), toTimeMillis );
    }

    private static int toTimeState(long ts1, long ts2, @DateTimeType String type,
                                  int last, int next) {
        int state = Integer.compare(
                ObjUtils.parseInt( DateTime.parse( ts2, type ) ),
                ObjUtils.parseInt( DateTime.parse( ts1, type ) )
        );
        return state == 0 ? TYPE_NONE : state == 1 ? next : last;
    }

    /**
     * 获取当前时区
     * @param daylight                      true：指定夏令时名称，false：指定标准时间名称
     * @param isShort                       true：GMT+08:00，false：中国夏令时间 或者 中国标准时间
     * @param isIncludeGmt                  true：GMT+08:00，false：+08:00
     * @param isIncludeMinuteSeparator      true：+08:00，false：+08
     * @return                              当前时区
     */
    public static String currentTimeZone(
            boolean daylight,
            boolean isShort, boolean isIncludeGmt,
            boolean isIncludeMinuteSeparator) {
        //true -> GMT+08:00 , false -> 中国夏令时间 或者 中国标准时间
        int style = isShort ? TimeZone.SHORT : TimeZone.LONG;
        String s = TimeZone.getDefault().getDisplayName( daylight, style );
        //不保留前缀
        if( !isIncludeGmt ) {
            if( s.contains( "GMT" ) ) s = s.replace( "GMT", "" );
            if( s.contains( "CDT" ) ) s = s.replace( "CDT", "" );
            if( s.contains( "CST" ) ) s = s.replace( "CST", "" );
        }
        //不保留后缀
        if( !isIncludeMinuteSeparator ) s = s.contains( ":" ) ? s.split(":")[ 0 ] : s;
        return s;
    }

    /**
     * 获取当前夏令时时区
     * @return  +18 ~ -18
     */
    public static String currentDaylightSavingTimeZone() {
        return currentTimeZone(
                true,
                true,
                false,
                false
        );
    }

    /**
     * 获取当前标准时区
     * @return  +18 ~ -18
     */
    public static String currentStandardTimeZone() {
        return currentTimeZone(
                false,
                true,
                false,
                false
        );
    }

    /**
     * 时区转秒
     * @param timeZone  时区
     * @return          秒
     */
    public static int timeZoneToSecond(String timeZone) {
        if( TextUtils.isEmpty( timeZone ) || timeZone.length() < 4 ) return 11;
        if( !timeZone.startsWith( "+" ) && !timeZone.startsWith( "-" ) ) return 22;
        if( !timeZone.contains( ":" ) ) return 33;
        if( timeZone.contains( "GMT" ) ) timeZone = timeZone.replace( "GMT", "" );
        if( timeZone.contains( "CDT" ) ) timeZone = timeZone.replace( "CDT", "" );
        if( timeZone.contains( "CST" ) ) timeZone = timeZone.replace( "CST", "" );

        int ret = 0;
        char first = timeZone.charAt( 0 );
        String[] sp = timeZone.substring( 1 ).split( ":" );
        ret += Integer.parseInt( sp[ 0 ] ) * 60 * 60;
        ret += Integer.parseInt( sp[ 1 ] ) * 60;
        return first == '+' ? ret : -ret;
    }

    /**
     * 时间进度  eg：00:00:00
     * @param locale            Locale
     * @param milliseconds      时间毫秒。eg：1000 -> 00:00:01
     * @param pattern           eg：{DateTimeType#DAY}
     * @return                  返回结果
     */
    public static String toTimeProgressFormat(Locale locale, long milliseconds, @NonNull String pattern) {
        long totalSeconds = milliseconds / 1000;
        long seconds = totalSeconds % 60;
        long minutes = ( totalSeconds / 60 ) % 60;
        long hours = totalSeconds / 3600;
        long days = totalSeconds / 86400;
        return pattern
                .replace( DAY, String.format( locale, "%02d", days ) )
                .replace( HOUR, String.format( locale, "%02d", hours ) )
                .replace( MINUTE, String.format( locale, "%02d", minutes ) )
                .replace( SECOND, String.format( locale, "%02d", seconds ) );
    }

    public static String toTimeProgressFormat(long milliseconds, @NonNull String pattern) {
        return toTimeProgressFormat( LocaleUtil.getLocale(), milliseconds, pattern );
    }

    public static long parse(String s, @NonNull Locale locale, @NonNull String... pattern) {
        int hours = 8;
        try {
            hours = Integer.parseInt( currentStandardTimeZone() );
        } catch (NumberFormatException ignored) { }
        if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
            return LocalDateTime
                    .parse( s, DateTimeFormatter.ofPattern( toPattern( pattern ), locale ) )
                    .toInstant( ZoneOffset.ofHours( hours ) )
                    .toEpochMilli();
        }else {
            Date date = null;
            try {
                date = new SimpleDateFormat( toPattern( pattern ), locale ).parse( s );
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return date == null ? 0 : date.getTime();
        }
    }

    public static long parse(String s, @NonNull String... pattern) {
        return parse( s, LocaleUtil.getLocale(), pattern );
    }

    public static long parse(String s) {
        return parse( s, getDefaultPattern() );
    }

    public static String parse(long s, @NonNull Locale locale, @NonNull String pattern) {
        int hours = 8;
        try {
            hours = Integer.parseInt( currentStandardTimeZone() );
        } catch (NumberFormatException ignored) { }

        if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
            //处理标准时间戳和Java时间戳
            s = Long.valueOf( s ).toString().length() <= 10 ? s : s / 1000L;
            return LocalDateTime
                    .ofEpochSecond( s, 0, ZoneOffset.ofHours( hours ) )
                    .format( DateTimeFormatter.ofPattern( pattern, locale ) );
        }else {
            return new SimpleDateFormat( pattern, locale ).format( s );
        }
    }

    public static String parse(long s, String... patterns) {
        return parse( s, LocaleUtil.getLocale(), toPattern( patterns ) );
    }

    public static String parse(long s) {
        return parse( s, getDefaultPattern() );
    }

    public static Map<String, String> parseOfList(long s) {
        Map<String, String> map = new HashMap<>();
        String[] keys = splitPatternsAll();
        String[] values = parse( s, keys ).split("\\|");
        for (int i = 0; i < keys.length; i++) {
            map.put( keys[ i ].replaceAll("\\|", ""), values[ i ] );
        }
        return map;
    }

    public static Map<String, String> nowOfList() { return parseOfList( currentTimeMillis() ); }

    public static String now(@NonNull Locale locale, @NonNull String pattern) {
        return parse( currentTimeMillis(), locale, pattern );
    }

    public static String now(@NonNull String pattern) {
        return now( LocaleUtil.getLocale(), pattern );
    }

    public static String nows(@NonNull String... patterns) {
        return parse( currentTimeMillis(), patterns );
    }

    public static String now(@NonNull String dateSymbol,
                             @NonNull String centerSymbol,
                             @NonNull String timeSymbol) {
        return parse( currentTimeMillis(), dateSymbol, centerSymbol, timeSymbol );
    }

    public static String now() { return parse( currentTimeMillis() ); }

    public static long nowTimeMillis(@NonNull String... patterns) {
        return DateTime.parse( DateTime.parse( System.currentTimeMillis(), patterns ) );
    }

    public static long nowTimeMillis() { return nowTimeMillis( getDefaultPattern() ); }

    public static String nowA() { return now( A ); }

    public static int get(long ts, int field) {
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis( ts );
        return c.get( field );
    }

    public static int get(String time, String pattern, int field) {
        return get( parse( time, pattern ), field );
    }

    public static int get(String time, int field) {
        return get( time, getDefaultPattern(), field );
    }

    public static int getYear(long ts) { return get( ts, Calendar.YEAR ); }

    public static int getMonth(long ts) { return get( ts, Calendar.MONTH ); }

    public static int getWeekOfYear(long ts) { return get( ts, Calendar.WEEK_OF_YEAR ); }

    public static int getWeekOfMonth(long ts) { return get( ts, Calendar.WEEK_OF_MONTH ); }

    public static int getDayOfWeekInMonth(long ts) {
        return get( ts, Calendar.DAY_OF_WEEK_IN_MONTH );
    }

    public static int getDayOfYear(long ts) { return get( ts, Calendar.DAY_OF_YEAR ); }

    public static int getDayOfWeek(long ts) { return get( ts, Calendar.DAY_OF_WEEK ); }

    public static int getDate(long ts) { return get( ts, Calendar.DATE ); }

    public static int getHour(long ts) { return get( ts, Calendar.HOUR ); }

    public static int getHourOfDay(long ts) { return get( ts, Calendar.HOUR_OF_DAY ); }

    public static int getMinute(long ts) { return get( ts, Calendar.MINUTE ); }

    public static int getMillis(long ts) { return get( ts, Calendar.MILLISECOND ); }

    public static int getSecond(long ts) { return get( ts, Calendar.SECOND ); }



    public static int getYear(String time) { return get( time, Calendar.YEAR ); }

    public static int getMonth(String time) { return get( time, Calendar.MONTH ); }

    public static int getWeekOfYear(String time) { return get( time, Calendar.WEEK_OF_YEAR ); }

    public static int getWeekOfMonth(String time) { return get( time, Calendar.WEEK_OF_MONTH ); }

    public static int getDayOfWeekInMonth(String time) {
        return get( time, Calendar.DAY_OF_WEEK_IN_MONTH );
    }

    public static int getDayOfWeek(String time) { return get( time, Calendar.DAY_OF_WEEK ); }

    public static int getDayOfYear(String time) { return get( time, Calendar.DAY_OF_YEAR ); }

    public static int getDate(String time) { return get( time, Calendar.DATE ); }

    public static int getHour(String time) { return get( time, Calendar.HOUR ); }

    public static int getHourOfDay(String time) { return get( time, Calendar.HOUR_OF_DAY ); }

    public static int getMinute(String time) { return get( time, Calendar.MINUTE ); }

    public static int getMillis(String time) { return get( time, Calendar.MILLISECOND ); }

    public static int getSecond(String time) { return get( time, Calendar.SECOND ); }

    public static int nowYear() { return getYear( currentTimeMillis() ); }

    public static int nowMonth() { return getMonth( currentTimeMillis() ); }

    public static int nowWeekOfYear() { return getWeekOfYear( currentTimeMillis() ); }

    public static int nowWeekOfMonth() { return getWeekOfMonth( currentTimeMillis() ); }

    public static int nowDayOfWeek() { return getDayOfWeek( currentTimeMillis() ); }

    public static int nowDayOfWeekInMonth() { return getDayOfWeekInMonth( currentTimeMillis() ); }

    public static int nowDayOfYear() { return getDayOfYear( currentTimeMillis() ); }

    public static int nowDate() { return getDate( currentTimeMillis() ); }

    public static int nowHour() { return getHour( currentTimeMillis() ); }

    public static int nowHourOfDay() { return getHourOfDay( currentTimeMillis() ); }

    public static int nowMinute() { return getMinute( currentTimeMillis() ); }

    public static int nowSecond() { return getSecond( currentTimeMillis() ); }

    public static int nowMillis() { return getMillis( currentTimeMillis() ); }
    
    public static long currentTimeMillis() { return System.currentTimeMillis(); }

    public static String getDefaultPattern() {
        return String.format(
                "%s-%s-%s %s:%s:%s",
                YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
        );
    }

    public static String toPattern(@NonNull String... s) {
        StringBuilder sb = new StringBuilder();
        for( String item : s ) sb.append( item );
        return sb.toString();
    }

    private static String[] splitPatternsAll() {
        String[] patterns = new String[] {
                YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLIS, WEEK_OF_YEAR, WEEK_LONG, A
        };
        for (int i = 0; i < patterns.length; i++) {
            if( i >= patterns.length - 1 ) break;
            patterns[ i ] = patterns[ i ] + '|';
        }
        return patterns;
    }
}
